package com.hotagent.reload;

import com.hotagent.LogUtil;
import lombok.Data;

import java.io.File;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@Data
public abstract class HotDeployer {
    private Result result;
    private Instrumentation ins;
    //热加载老类
    List<ClassDefinitionWrap> redefineOldClassesWrap = new ArrayList<>();
    //热加载新类
    List<ClassDefinitionWrap> redefineNewClassesWrap = new ArrayList<>();
    //热加载完后要执行的类(类名-类)
    Map<String, DelayClassDefinitionWrap> delayExecClassesWrap = new HashMap<>();

    public HotDeployer(Result result, Instrumentation ins) {
        super();
        this.result = result;
        this.ins = ins;
    }

    /**
     * 获取热加载jar包path
     */
    protected abstract String getHotDeployJarPath();

    /**
     * 是否必选，必选的并且没有找到要处理的文件，则中断整个热加载过程
     */
    protected abstract boolean isRequired();

    public Result exec() {
        File file = new File(getHotDeployJarPath());
        LogUtil.hotAgent.info(file.getAbsolutePath());
        if (!file.exists()) {
            if (isRequired()) {
                result.appendMsg("未找到jar包：" + getHotDeployJarPath());
                result.setResult(false);
            } else {
                result.setResult(true);
            }
            return result;
        }
        result.appendMsg("已找到jar包：" + getHotDeployJarPath());

        try {
            JarFile jarFile = new JarFile(file);
            Enumeration<JarEntry> enumeration = jarFile.entries();
            while (enumeration.hasMoreElements()) {
                JarEntry entry = enumeration.nextElement();
                String name = entry.getName();
                if (!name.endsWith(".class")) {
                    continue;
                }
                String classname = name.replace('/', '.');
                classname = classname.substring(0, classname.lastIndexOf('.'));
                InputStream inputStream = jarFile.getInputStream(entry);
                byte[] bs = Helper.toBytes(inputStream);
                Class<?> cls = null;
                cls = Class.forName(classname);
                if (cls != null) {
                    byte[] originalBs = null;
                    InputStream originalInputStream = cls.getResourceAsStream("/" + name);
                    if (originalInputStream != null) {
                        originalBs = Helper.toBytes(originalInputStream);
                    }
                    //先加载新类，后加载老类
                    redefineOldClassesWrap.add(new ClassDefinitionWrap(cls, bs, originalBs, classname));
                } else {
                    redefineNewClassesWrap.add(new ClassDefinitionWrap(null, bs, null, classname));
                }
            }
            //加载新类
            for (ClassDefinitionWrap newClassWrap : redefineNewClassesWrap) {
                String classname = newClassWrap.getClassname();
                byte[] bs = newClassWrap.getBs();
                if (!defineNewClass(classname, bs)) {
                    result.appendMsg("热加载-新类" + classname + "失败1");
                    result.setResult(false);
                    return result;
                }
                //其他检测
                Class<?> cls = null;
                try {
                    cls = Class.forName(classname);
                } catch (ClassNotFoundException e) {
                    result.appendMsg("热加载-新类" + classname + "失败2");
                    result.setResult(false);
                    return result;
                }
                if (cls == null) {
                    result.appendMsg("热加载-新类" + classname + "失败3");
                    result.setResult(false);
                    return result;
                }
                try {
                    tryAppendDelayClassDefinitionWrap(cls, null);
                } catch (Exception e) {
                    result.appendMsg("热加载-新类" + classname + "失败4");
                    result.setResult(false);
                    return result;
                }
                result.appendMsg("热加载-新类" + classname + "成功");
            }

            //加载老类
            for (ClassDefinitionWrap oldClassWrap : redefineOldClassesWrap) {
                Class<?> cls = oldClassWrap.getCls();
                String classname = oldClassWrap.getClassname();
                byte[] bs = oldClassWrap.getBs();
                byte[] originalBs = oldClassWrap.getOriginalBs();
                try {
                    ins.redefineClasses(new ClassDefinition(cls, bs));
                    tryAppendDelayClassDefinitionWrap(cls, originalBs);
                } catch (Exception e) {
                    result.appendMsg("热加载-老类" + classname + "失败1");
                    result.setResult(false);
                    return result;
                }
                result.appendMsg("热加载-老类" + classname + "成功");
            }
            //执行延迟执行类
            result.appendMsg("热加载" + getHotDeployJarPath() + "成功");
            jarFile.close();
        } catch (Exception e) {
            result.appendMsg("热加载" + getHotDeployJarPath() + "失败" + e.getMessage());
            result.setResult(false);
        }
        return this.result;
    }

    private void tryAppendDelayClassDefinitionWrap(Class<?> cls, byte[] bs) {
        if (this instanceof HotDeployHotDeployer) {
            if (this.delayExecClassesWrap.containsKey(cls.getName())) {
                return;
            }
            this.delayExecClassesWrap.put(cls.getName(), new DelayClassDefinitionWrap(cls, bs));
        }
    }

    private void execDelayClass() {
        for (Map.Entry<String, DelayClassDefinitionWrap> entry : this.delayExecClassesWrap.entrySet()) {
            execDelayClass(entry.getValue());
        }
    }

    private void execDelayClass(DelayClassDefinitionWrap delay) {
        Class<?> cls = delay.getCls();
        try {
            Method method = cls.getMethod("exec", null);
            method.invoke(null, null);
            byte[] originalBs = delay.getBs();
            if (originalBs != null) {
                ins.redefineClasses(new ClassDefinition(cls, originalBs));
            }
            result.appendMsg("热加载执行" + cls.getName() + "成功");
        } catch (Exception e) {
            result.appendMsg("热加载执行" + cls.getName() + "失败");
            throw new RuntimeException("热加载执行" + cls.getName() + "错误");
        }
    }

    private boolean defineNewClass(String className, byte[] bs) {
        ClassLoader classLoader = this.getClass().getClassLoader();
        Method method = Helper.getMethod(classLoader.getClass(), "defineClass", String.class, byte[].class, int.class, int.class);
        if (null == method) {
            result.appendMsg("ClassLoader 找不到 defineClass 方法");
            return false;
        }
        method.setAccessible(true);
        try {
            method.invoke(classLoader, className, bs, 0, bs.length);
        } catch (Exception e) {
            result.appendMsg("ClassLoader.defineClass 错误" + e.getMessage());
            return false;
        }
        return true;
    }
}
